import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.NodeVisitor;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.SwitchCase;
import org.mozilla.javascript.ast.Symbol;
import org.mozilla.javascript.ast.TryStatement;

/**
 * @author Jorge Villasmil
 *
 */
public class FeatureExtractor implements NodeVisitor 
{
	private static ContextStack contexts = new ContextStack();
	private static List<Integer> elsePartLineNos = new ArrayList<Integer>();
	private static List<Integer> finallyPartLineNos = new ArrayList<Integer>();
	private static List<String> flatLevelFeatures = new ArrayList<String>();
	private static Map<String, ArrayList<String>> oneLevelMap = new HashMap<String,  
																ArrayList<String>>();
	private static Map<String, ArrayList<String>> nLevelMap = new HashMap<String, 
																ArrayList<String>>();
	private static Map<String, String> identifierTable = new HashMap<String, String>();
	static int count = 0;
	static String prevIdentName = ""; 
	
	
	/**
	 * 
	 * @param text
	 * @param strContext
	 * @param levelMap
	 */
	private void createOneAndNLevelFeatures(String text, String strContext, 
											Map<String, ArrayList<String>> levelMap) 
	{
		ArrayList<String> featuresInSameContext;			
		
		if(levelMap.containsKey(strContext))
			featuresInSameContext = levelMap.get(strContext);
		else
			featuresInSameContext = new ArrayList<String>();
	
		featuresInSameContext.add(text);
		levelMap.put(strContext, featuresInSameContext);
	}
	
	/**
	 * 
	 * @param text
	 */
	private void createFeatures(String text)
	{
		flatLevelFeatures.add(text);
		if(contexts.isEmpty())
			return;
		
		createOneAndNLevelFeatures(text, "< " + contexts.peekName()
									+ " > ", oneLevelMap);
		createOneAndNLevelFeatures(text, contexts.toString(), nLevelMap);		
	}
	
	/**
	 * 
	 * @param node
	 * @param elseOrFinallyPartLineNos
	 * @return
	 */
	private boolean isAnElseOrFinallyPart(AstNode node, 
								List<Integer> elseOrFinallyPartLineNos)
	{				
		for(int i = 0; i < elseOrFinallyPartLineNos.size(); i++)
		{
			if(node.getLineno() == elseOrFinallyPartLineNos.get(i))
			{
				elseOrFinallyPartLineNos.remove(i);
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * 
	 * @return
	 */
	public List<String> getFlatLevelFeatures()
	{
		return flatLevelFeatures;
	}
	
	/**
	 * 
	 * @return
	 */
	public Map<String, ArrayList<String>> getOneLevelFeatures()
	{
		return oneLevelMap;
	}
	
	/**
	 * 
	 * @return
	 */
	public Map<String, ArrayList<String>> getNLevelFeatures()
	{
		return nLevelMap;
	}
	/*
	private void printSymbols(AstNode node)
	{
		//System.out.println("You've reached first step");
		
		
		if(node.getEnclosingScope() != null)
		{
			System.out.println("Scope isnt null!");
			Map<String, Symbol> symTable = node.getEnclosingScope().getSymbolTable();
			
			if(symTable != null)
			{
				System.out.println("Symbol table isnt null!");
				for(Map.Entry<String, Symbol> entry : symTable.entrySet())
				{
					System.out.println("String: " + entry.getKey() + ", Symbol: " 
										+ entry.getValue().getContainingTable());
				}
			}			
		}				
	}*/
	
	/**
	 * 
	 */
	public boolean visit(AstNode node) 
	{		
//		System.out.println("Do I have Childen? " + node.hasChildren());
		String nodeName = node.shortName();
		String nodeParentName = "";
		AstNode nodeParent = node.getParent();
		int nodeDepth = node.depth();
		//boolean wasPrevNodeAName = false;
		
		if(nodeParent != null)
			nodeParentName = nodeParent.shortName();
						
//		for(int i = 0; i < nodeDepth; i++ )
			//System.out.print("\t");
		//System.out.println(nodeName);
		
		while(!contexts.isEmpty() && nodeDepth <= contexts.peekDepth())
			contexts.popName(); 						
		
		if(isAnElseOrFinallyPart(node, finallyPartLineNos))
			contexts.push("finally", nodeDepth);
		
		if(nodeName.equals("FunctionNode"))
		{					
			contexts.push("function", nodeDepth);
		}
		else if(nodeName.equals("SwitchStatement"))
		{
			contexts.push("switch", nodeDepth);
		}
		else if(nodeName.equals("TryStatement"))
		{
			AstNode finallyPart = ((TryStatement)node).getFinallyBlock();
			if(finallyPart != null) 				
			{
				int lineNo = finallyPart.getLineno();
				finallyPartLineNos.add(lineNo);
				//System.out.println("Adding lineNo: " + lineNo);
			}				
			
			contexts.push("try", nodeDepth);
		}
		else if(nodeName.equals("CatchClause"))
		{
			if(contexts.peekName().equals("try"))
				contexts.popName();
			
			contexts.push("catch", nodeDepth);
		}
		//Else statements are not IfStatement nodes, but else-ifs are.
		//Only want to add a context if it's an if statement, not an else-if
		else if(nodeName.equals("IfStatement"))
		{		
			AstNode elsePart = ((IfStatement)node).getElsePart();
			if(elsePart != null) 				
			{
				int lineNo = elsePart.getLineno();
				elsePartLineNos.add(lineNo);
				//System.out.println("Adding lineNo: " + lineNo);
			}				
			
			//If the line is an if statement
			if(!isAnElseOrFinallyPart(node, elsePartLineNos)) 
				contexts.push("if", nodeDepth);																										
		}
		else if(nodeName.equals("ForInLoop") 
				|| nodeName.equals("ForLoop") 
				|| nodeName.equals("WhileLoop") 
				|| nodeName.equals("DoLoop")
				|| nodeName.equals("ArrayComprehensionLoop"))
		{
			contexts.push("loop", nodeDepth);
		}				
		else if(nodeName.equals("Name") 
			&& !nodeParentName.equals("FunctionCall")
			&& !nodeParentName.equals("UnaryExpression")
			&& !nodeParentName.equals("ElementGet")
			&& !nodeParentName.equals("NewExpression"))
		{
			//System.out.print("\n" + contexts + node.getString());
			createFeatures(node.getString());
			//wasPrevNodeAName = true;
			
			//node.get
			//printSymbols(node);
		}	
		else if(nodeName.equals("ReturnStatement"))
		{
			String nodeSrc = node.toSource();
			String nodeText = nodeSrc.substring(0, nodeSrc.length() - 1);
			
			//System.out.print("\n" + contexts + nodeText);
			createFeatures(nodeText);
		}
		else if(nodeName.equals("NumberLiteral")
				&& !nodeParentName.equals("ElementGet"))
		{
			//System.out.print("\n" + contexts + ((NumberLiteral)node).getValue());
			createFeatures(((NumberLiteral)node).getValue());
		}								
		else if(nodeName.equals("SwitchCase"))
		{
			AstNode switchCaseExp = ((SwitchCase)node).getExpression();
			if(switchCaseExp != null)
			{
				//System.out.print("\n" + contexts + "case: " 
						//		 + switchCaseExp.toSource());
				
				createFeatures("case: " + switchCaseExp.toSource());
			}
			else
			{
			//	System.out.print("\n" + contexts + "default:");
				createFeatures("default: " );
			}
		}				
		else if(nodeName.equals("ElementGet")
				&& !nodeParentName.equals("UnaryExpression"))
		{
			//System.out.print("\n" + contexts + node.toSource());
			createFeatures(node.toSource());
		}
		else if(nodeName.equals("StringLiteral")
				|| (nodeName.equals("ArrayLiteral")) 
				|| (nodeName.equals("ArrayComprehension")) 
				|| (nodeName.equals("InfixExpression")) 
				|| (nodeName.equals("EmptyExpression")) 
				|| (nodeName.equals("ParenthesizedExpression")) 
				|| (nodeName.equals("VariableInitializer")) 
				|| (nodeName.equals("UnaryExpression")) 
				|| (nodeName.equals("FunctionCall"))  
				|| (nodeName.equals("Assignment")) 
				|| (nodeName.equals("KeywordLiteral")) 
				|| (nodeName.equals("ObjectProperty")) 
				|| (nodeName.equals("ObjectLiteral")) 
				|| (nodeName.equals("BreakStatement")) 
				|| (nodeName.equals("ContinueStatement")) 
				|| (nodeName.equals("ConditionalExpression")) 
				|| (nodeName.equals("NewExpression")) 
				|| (nodeName.equals("RegExpLiteral")) 
				|| (nodeName.equals("Label"))
				|| (nodeName.equals("ThrowStatement"))
				|| (nodeName.equals("PropertyGet"))
				|| (nodeName.equals("XmlDotQuery"))
				|| (nodeName.equals("XmlElemRef"))
				|| (nodeName.equals("XmlExpression"))
				|| (nodeName.equals("XmlLiteral"))
				|| (nodeName.equals("XmlString")))
		{
			//System.out.print("\n" + contexts + node.toSource());
			createFeatures(node.toSource());
		}				
			
		if(nodeName.equals("VariableInitializer") || nodeName.equals("Assignment"))
		{		
			//System.out.println("This one's a VI or an ASSIgn");
			class FeatureFlow implements NodeVisitor 
			{	
		        public boolean visit(AstNode node) 
		        {		   
		        	count++;
		        	//System.out.println("Visit# : " + count);
		        	String strNode = node.shortName();
		        	
		        	if(count > 3)
		        	{
		        		//Systemout.println("sorry count is > 3: " + count);
		        		identifierTable.remove(prevIdentName);
		        		return false;
		        	}
		        	
		        	if(count == 2)
		        	{
		        		if(strNode.equals("Name"))
		        		{
		        			prevIdentName = node.getString();
			        		//System.out.println("This One's a left name!");
		        		}
		        		else
		        			return false;
		        	}
		       
		        		
		        	if(count == 3)
		        	{
		        		if(strNode.equals("StringLiteral"))
		        		{
		        			identifierTable.put(prevIdentName, node.toSource());
		        			//System.out.println("This One's a StringLiteral!");
		        		}
		        		else if(strNode.equals("NumberLiteral"))
		        		{
		        			identifierTable.put(prevIdentName, 
		        								((NumberLiteral)node).getValue());
		        			//System.out.println("This One's a NumberLiteral!");
		        		}
		        		else if(strNode.equals("Name"))
		        		{
		        			//System.out.println("This One's a Right Name!");
		        			String rawValue = identifierTable.get(node.getString());
		        			//System.out.println("CHECK IT OUT: " + prevIdentName + " = " + rawValue);
		        			createFeatures(rawValue);
		        			createFeatures(prevIdentName);
		        		}
		        		else
		        			return false;
		        	}		 
		        			        			           
		        	return true;
		        }
		    }
			
			node.visit(new FeatureFlow());
			count = 0;
			prevIdentName = "";
			
		}
			
		//Systemout.println();
		return true;
	}			
}